﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Net.Mail;
using ArgusLib.Controls;
using ArgusLib.Threading;
using ArgusLib.Threading.Collections;
using FreeImageAPI;
using FreeImageAPI.Metadata;
using System.Globalization;

namespace Imagenator
{
	partial class MainForm : ArgusLib.Controls.BaseForm
	{
		PatternEngine.Handler patternHandler;
		ISaveControl saveControl;
		Thread backgroundWorker;
		SynchronizedStack<object> syncedFileStack;
		ArgusLib.Threading.ThreadState threadState;
		Forms.ImageForm formsImageForm;
		ArgusLib.Action backgroundWorkerFinishedAction;

		public MainForm()
		{
			InitializeComponent();
			this.MinimumClientSize = new Size(575, 300);
			this.lbFiles.AllowedKeyShortcuts = ArgusLib.Controls.ListBox.KeyShortcuts.All;
			this.syncedFileStack = new SynchronizedStack<object>();
			this.threadState = new ArgusLib.Threading.ThreadState();
		}

		static ISaveControl GetSaveFlagInputControl(FREE_IMAGE_FORMAT format)
		{
			if (format == FREE_IMAGE_FORMAT.FIF_JPEG)
				return new Controls.JpegSaveControl();
			else if (format == FREE_IMAGE_FORMAT.FIF_PNG)
				return new Controls.PngSaveControl();
			else if (format == FREE_IMAGE_FORMAT.FIF_EXR)
				return new Controls.ExrSaveControl();
			else if (format == FREE_IMAGE_FORMAT.FIF_ICO)
				return new Controls.IcoSaveControl();

			return new Controls.SaveControl();
		}

		protected override void OnLoad(EventArgs e)
		{
			this.Text = "Imagenator v" + ArgusLib.Environment.GetAssemblyVersion().ToString();

			StringBuilder filter = new StringBuilder();
			StringBuilder allFormats = new StringBuilder();
			Array formats = Enum.GetValues(typeof(FREE_IMAGE_FORMAT));
			foreach (FREE_IMAGE_FORMAT format in formats)
			{
				if (FreeImage.FIFSupportsReading(format) == false)
					continue;
				string extensions = FreeImage.GetFIFExtensionList(format);
				if (string.IsNullOrEmpty(extensions) == true)
					continue;
				extensions = "*." + extensions.Replace(",", ";*.");
				allFormats.Append(extensions);
				allFormats.Append(';');
				filter.Append('|');
				filter.Append(FreeImage.GetFIFDescription(format));
				filter.Append('|');
				filter.Append(extensions);
			}
			allFormats.Remove(allFormats.Length - 1, 1);
			filter.Insert(0, "All Supported Formats|" + allFormats.ToString());
			this.ofdInput.Filter = filter.ToString();
			this.ofdInput.FilterIndex = 0;

			this.patternHandler = PatternEngine.GetHandler(this.tbPattern.Text);

			foreach (PatternEngine.Placeholder placeholder in PatternEngine.Placeholders)
			{
				this.lbVars.Items.Add('{' + placeholder.Identifier + "}: " + placeholder.Description);
			}

			this.formsImageForm = new Forms.ImageForm();

			this.LoadSettings();

			base.OnLoad(e);

			if (Settings.Load().CheckForUpdates == true)
			{
				this.cbCheckUpdates_Click(null, EventArgs.Empty);
			}
		}

		private void LoadSettings()
		{
			Settings settings = Settings.Load<Settings>();
			this.cbFormat.Select(settings.OutputFormat);

			this.tbPattern.Text = settings.OutputPattern;

			if (settings.MainFormBounds.IsEmpty == false)
			{
				Point topLeft = new Point(settings.MainFormBounds.Left, settings.MainFormBounds.Top);
				Screen screen = Screen.FromPoint(topLeft);
				if (screen.WorkingArea.Contains(topLeft) == true)
					this.Bounds = settings.MainFormBounds;
			}

			if (settings.ImageFormBounds.IsEmpty == false)
			{
				Point topLeft = new Point(settings.ImageFormBounds.Left, settings.ImageFormBounds.Top);
				Screen screen = Screen.FromPoint(topLeft);
				if (screen.WorkingArea.Contains(topLeft) == true)
					this.formsImageForm.Bounds = settings.ImageFormBounds;
			}

			if (settings.ShowPreviewImages == true)
			{
				this.lbFiles.Appearance = ArgusLib.Controls.ListBox.Appearances.ImageAndText;
				this.lbFiles.ItemHeight = settings.ThumbnailSize.Height;
				this.lbFiles.ImageWidth = settings.ThumbnailSize.Width;
				this.threadState.Resume();
			}
			else
			{
				this.lbFiles.Appearance = ArgusLib.Controls.ListBox.Appearances.Text;
				this.lbFiles.ItemHeight = this.lbFiles.Font.Height;
				this.threadState.Suspend();
			}
		}

		private void SaveSettings()
		{
			Settings settings = Settings.Load<Settings>();
			settings.OutputFormat = this.cbFormat.SelectedItem;
			settings.OutputPattern = this.tbPattern.Text;
			settings.MainFormBounds = this.Bounds;
			settings.ImageFormBounds = this.formsImageForm.Bounds;

			if (this.saveControl != null)
				this.saveControl.Close();

			settings.Save();
		}

		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			if (this.backgroundWorker != null && this.backgroundWorker.IsAlive == true)
			{
				this.backgroundWorkerFinishedAction = new ArgusLib.Action(() => { this.Close(); });
				this.threadState.RequestCancel();
				e.Cancel = true;
				this.Enabled = false;
			}
			this.SaveSettings();
			base.OnFormClosing(e);
		}

		private void BackgroundWorker_Finished()
		{
			this.backgroundWorker = null;
			if (this.backgroundWorkerFinishedAction != null)
				this.backgroundWorkerFinishedAction();
		}

		private void AddFiles(string[] filenames)
		{
			ThreadPool.QueueUserWorkItem(new WaitCallback((arg) =>
				{
					object[] keys = new object[filenames.Length];
					for (int i = 0; i < filenames.Length; i++)
						keys[i] = filenames[i];
					this.lbFiles.Invoke(new ArgusLib.Action(() => { this.lbFiles.Items.AddRange(keys); }));

					if (this.backgroundWorker == null || this.backgroundWorker.IsAlive == false)
					{
						this.backgroundWorker = new Thread(new ThreadStart(() =>
						{
							Parallelization.TaskBody[] tasks = this.AddFilesToBackgroundWorker(filenames);
							Parallelization.ProcessTasks(tasks, this.threadState);
							this.Invoke(new ArgusLib.Action(this.BackgroundWorker_Finished));
						}));
						this.backgroundWorker.Start();
					}
					else
					{
						this.syncedFileStack.PushRange(keys);
					}
				}));
		}

		private Parallelization.TaskBody[] AddFilesToBackgroundWorker(object[] keys)
		{
			Parallelization.TaskBody[] tasks = new Parallelization.TaskBody[keys.Length];
			Settings settings = Settings.Load<Settings>();
			for (int i = 0; i < keys.Length; i++)
			{
				object key = keys[i];
				string filename = (string)key;
				tasks[i] = new Parallelization.TaskBody((threadState) =>
				{
					if (threadState.CancelRequested == true)
						return null;
					if (settings.ShowPreviewImages == true)
					{
						FreeImageBitmap fib;
						FREE_IMAGE_LOAD_FLAGS loadFlags = FREE_IMAGE_LOAD_FLAGS.DEFAULT;
						if (FreeImage.GetFIFFromFilename(filename) == FREE_IMAGE_FORMAT.FIF_RAW)
							loadFlags |= FREE_IMAGE_LOAD_FLAGS.RAW_PREVIEW | FREE_IMAGE_LOAD_FLAGS.RAW_DISPLAY;
						try { fib = new FreeImageBitmap(filename, loadFlags); }
						catch { fib = null; }
						if (fib != null)
						{
							using (fib)
							{
								if (threadState.CancelRequested == true)
									return null;
								if (fib.ImageFormat != FREE_IMAGE_FORMAT.FIF_UNKNOWN)
								{
									Bitmap bitmap;
									try { bitmap = fib.ToBitmap(); }
									catch { bitmap = null; }
									if (threadState.CancelRequested == true)
										return null;
									if (bitmap != null)
										this.lbFiles.Invoke(new ArgusLib.Action(() => { this.lbFiles.Images.Add(key, bitmap); }));
								}
							}
						}
					}
					object[] newKeys = this.syncedFileStack.Empty();;
					return this.AddFilesToBackgroundWorker(newKeys);
				});
			}
			return tasks;
		}

		private void tbOutputPath_DoubleClick(object sender, EventArgs e)
		{
			if (this.fsdOutput.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				this.tbOutputPath.Text = this.fsdOutput.SelectedPath;
		}

		private void cmLbFilesAddFiles_Click(object sender, EventArgs e)
		{
			if (this.ofdInput.ShowDialog() != System.Windows.Forms.DialogResult.OK)
				return;

			this.AddFiles(this.ofdInput.FileNames);
		}

		private void cmLbFilesAddFolders_Click(object sender, EventArgs e)
		{
			if (this.fsdInput.ShowDialog() != System.Windows.Forms.DialogResult.OK)
				return;

			this.AddFolders(this.fsdInput.SelectedPaths);
		}

		private void AddFolders(IEnumerable<string> folders)
		{
			ThreadPool.QueueUserWorkItem(new WaitCallback((args) =>
				{
					List<string> filenames = new List<string>();
					foreach (string dir in folders)
					{
						string[] files = Directory.GetFiles(dir, "*", SearchOption.AllDirectories);
						foreach (string file in files)
						{
							if (FreeImage.FIFSupportsReading(FreeImage.GetFIFFromFilename(file)) == true)
								filenames.Add(file);
						}
					}
					this.AddFiles(filenames.ToArray());
				}));
		}

		private void lbFiles_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.lbFiles.SelectedIndex < 0)
			{
				this.tbPathPreview.Text = null;
				return;
			}

			string imagePath = (string)this.lbFiles.SelectedItem;
			int number = this.lbFiles.SelectedIndex;
			FREE_IMAGE_FORMAT format = this.cbFormat.SelectedItem;

			System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback((arg) =>
				{
					ImageInfo imageInfo = ImageInfo.GetInfo(imagePath, number);
					if (this.patternHandler != null)
					{
						string path = Path.Combine(this.tbOutputPath.Text, this.patternHandler(imageInfo));
						string ext = FreeImage.GetPrimaryExtensionFromFIF(format);
						if (string.IsNullOrEmpty(ext) == false)
							path += '.' + ext;

						this.Invoke(new ArgusLib.Action(() => { this.tbPathPreview.Text = path; }));
					}

					if (this.formsImageForm.IsShown == true && this.formsImageForm.Visible == true)
					{
						this.SetImageFormBackground(imagePath);
					}
				}));
		}

		private void tbPattern_TextChanged(object sender, EventArgs e)
		{
			this.patternHandler = PatternEngine.GetHandler(this.tbPattern.Text);
		}

		private void lbVars_DoubleClick(object sender, EventArgs e)
		{
			if (this.lbVars.SelectedIndex < 0)
				return;
			string s = (string)this.lbVars.SelectedItem;
			int i = s.IndexOf(':');
			this.tbPattern.Insert(s.Substring(0, i));
		}

		private void cbFormat_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.saveControl != null)
				this.saveControl.Close();

			this.pSaveControl.Controls.Clear();
			this.saveControl = MainForm.GetSaveFlagInputControl(this.cbFormat.SelectedItem);
			if (this.saveControl != null)
			{
				this.saveControl.SetFormat(this.cbFormat.SelectedItem);
				Control c = this.saveControl.Control;
				c.Dock = DockStyle.Fill;
				this.pSaveControl.Controls.Add(c);
			}
		}

		private void bStart_Click(object sender, EventArgs e)
		{
			if (this.lbFiles.Items.Count < 1)
			{
				ArgusLib.Controls.MessageBox.Show(Localization.Text.MBNoImagesText, Localization.Text.MBNoImagesCaption);
				return;
			}

			if (this.patternHandler == null)
			{
				ArgusLib.Controls.MessageBox.Show(
					string.Format(Localization.Text.MBInvalidPatternText, this.tbPattern.Text), Localization.Text.MBInvalidPatternCaption);
				return;
			}

			DirectoryInfo outputDir = this.tbOutputPath.DirectoryInfo;

			if (outputDir == null || outputDir.TryCreate() == false)
			{
				ArgusLib.Controls.MessageBox.Show(
					string.Format(Localization.Text.MBInvalidOutputPathText, this.tbOutputPath.Text), Localization.Text.MBInvalidOutputPathCaption);
				return;
			}

			if (this.saveControl.BeginSaving() == false)
				return;

			this.threadState.Suspend();

			string ext = FreeImage.GetPrimaryExtensionFromFIF(this.cbFormat.SelectedItem);
			if (string.IsNullOrEmpty(ext) == false)
				ext = '.' + ext;
			char[] invalidChars = Path.GetInvalidPathChars();

			SynchronizedListWrapper<string> failedFiles = new SynchronizedListWrapper<string>(new List<string>());

			ProgressBox.ShowFor(this.lbFiles.Items.Count, new ArgusLib.Threading.Parallelization.ForBody((i, ts)=>
				{
					ProgressBox.ThreadState threadState = (ProgressBox.ThreadState)ts;
					FileInfo srcFile = new FileInfo((string)this.lbFiles.Items[i]);
					if (!srcFile.Exists)
					{
						failedFiles.Add(srcFile.FullName);
						return;
					}

					FREE_IMAGE_LOAD_FLAGS loadFlags = FREE_IMAGE_LOAD_FLAGS.DEFAULT;
					//if (FreeImage.GetFIFFromFilename(srcFile.FullName) == FREE_IMAGE_FORMAT.FIF_RAW)
					//	loadFlags |= FREE_IMAGE_LOAD_FLAGS.RAW_PREVIEW;
	
					FreeImageBitmap image;
					try{image = new FreeImageBitmap(srcFile.FullName, loadFlags);}
					catch{failedFiles.Add(srcFile.FullName);return;}

					using (image)
					{
						FileInfo destFile = null;
						{
							ImageInfo imageInfo = ImageInfo.GetInfo(image, i, srcFile.FullName);
							//if (imageInfo == null)
							//	imageInfo = new ImageInfo(File.GetCreationTime(srcFile.FullName), i, srcFile.FullName);

							string newExt = ext;
							if (string.IsNullOrEmpty(newExt) == true)
								newExt = Path.GetExtension(srcFile.FullName);

							string name = this.patternHandler(imageInfo).RemoveChars(invalidChars);
							destFile = outputDir.CombineFile(name + newExt);
							for (int j = 1; j < int.MaxValue; j++)
							{
								if (destFile == null)
								{
									throw new IOException(string.Format("{0}: Invalid Filename", name));
								}
								if (destFile.Exists == false)
									break;
								destFile = outputDir.CombineFile(string.Format("{0} ({1}){2}", name, j, newExt));
							}

						}

						destFile.Directory.Create();

						try{this.saveControl.SaveImage(image, srcFile, destFile, threadState);}
						catch{failedFiles.Add(srcFile.FullName);}
					}
				}), true);

			this.saveControl.EndSaving();

			if (failedFiles.Count > 0)
				Forms.FailedFilesBox.Show(failedFiles.ToArray());

			if (Settings.Load<Settings>().ShowPreviewImages == true)
				this.threadState.Resume();
		}

		#region Drag'n'Drop
		private void lbFiles_DragEnter(object sender, DragEventArgs e)
		{
			if (e.AllowedEffect.HasFlag(DragDropEffects.Link) == false || e.Data.GetDataPresent(DataFormats.FileDrop) == false)
			{
				e.Effect = DragDropEffects.None;
			}
			else
			{
				e.Effect = DragDropEffects.Link;
			}
		}

		private void lbFiles_DragDrop(object sender, DragEventArgs e)
		{
			if (e.Effect != DragDropEffects.Link)
				return;

			string[] input = e.Data.GetData(DataFormats.FileDrop) as string[];
			if (input == null)
				return;

			List<string> filenames = new List<string>();
			foreach (string path in input)
			{
				if (Directory.Exists(path) == true)
				{
					string[] files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
					foreach (string file in files)
					{
						if (FreeImage.FIFSupportsReading(FreeImage.GetFIFFromFilename(file)) == true)
							filenames.Add(file);
					}
				}
				else if (FreeImage.FIFSupportsReading(FreeImage.GetFIFFromFilename(path)) == true)
				{
					filenames.Add(path);
				}
			}
			this.AddFiles(filenames.ToArray());
		}
		#endregion

		private void lbFiles_DoubleClick(object sender, EventArgs e)
		{
			if (this.lbFiles.SelectedIndex < 0)
				return;

			if (this.formsImageForm.IsShown == true && this.formsImageForm.Visible == true)
				return;

			string path = (string)this.lbFiles.SelectedItem;

			System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback((arg) =>
				{
					this.SetImageFormBackground(path);
				}));
		}

		private void SetImageFormBackground(string path)
		{
			this.Invoke(new ArgusLib.Action(() => { this.saveControl.BeginSaving(); }));
			using (FreeImageBitmap fib = new FreeImageBitmap(path))
			{
				using (MemoryStream buffer = new MemoryStream(fib.DataSize))
				{
					this.saveControl.SaveImage(fib, buffer);
					buffer.Seek(0, SeekOrigin.Begin);
					Image image;
					try { image = Image.FromStream(buffer); }
					catch { image = null; }

					if (image != null)
					{
						this.formsImageForm.BackgroundImage = image;
						this.Invoke(new ArgusLib.Action(() => { this.formsImageForm.Show(); }));
					}
					else
					{
						this.Invoke(new ArgusLib.Action(() => { ArgusLib.Controls.MessageBox.Show(Localization.Text.MBFormatDoesntSupportDisplayText, Localization.Text.MBFormatDoesntSupportDisplayCaption); }));
					}
				}
			}
			this.Invoke(new ArgusLib.Action(() => { this.saveControl.EndSaving(); }));
		}

		private void tsmiSettings_Click(object sender, EventArgs e)
		{
			this.SaveSettings();
			Forms.SettingsForm settingsForm = new Forms.SettingsForm(this.lbFiles.Font.Height);
			settingsForm.ShowDialog();
			this.LoadSettings();
		}

		private void lbFiles_KeyShortcutSelectedRemoved(object sender, ArgusLib.Controls.ListBox.KeyShortcutSelectedRemovedEventArgs e)
		{
			//this.lbFiles.Images.RemoveKeyRange(e.RemovedItems);
		}

		private void tsmiHelpHomepage_Click(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("https://imagenator.codeplex.com");
		}

		private void tsmiHelpDocumentation_Click(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("https://imagenator.codeplex.com/documentation");
		}

		private void cbCheckUpdates_Click(object sender, EventArgs e)
		{
			UpdateInfo.DownloadAsync(new Action<UpdateInfo>((updateInfo) =>
			{
				if (updateInfo != null && updateInfo.BuildVersion > ArgusLib.Environment.GetAssemblyVersion())
				{
					this.Invoke(new ArgusLib.Action(() => { ArgusLib.Controls.UpdateInfoBox.Show(updateInfo); }));
				}
				else if (sender != null)
				{
					this.Invoke(new ArgusLib.Action(() =>
					{
						ArgusLib.Controls.MessageBox.Show(Localization.Text.MBNoUpdateText, Localization.Text.MBNoUpdateCaption);
					}));
				}
			}));
		}

		private void cmLbFilesImportFromDrive_Click(object sender, EventArgs e)
		{
			Forms.DrivesForm drivesForm = new Forms.DrivesForm();
			if (drivesForm.ShowDialog() != System.Windows.Forms.DialogResult.OK)
				return;

			this.AddFolders(new string[]{drivesForm.DriveRoot});
		}

		private void tsmiHelpSendFeedback_Click(object sender, EventArgs e)
		{
			Forms.FeedbackForm feedbackForm = new Forms.FeedbackForm();
			feedbackForm.ShowDialog();
		}
	}
}
